001 /* 002 * Copyright 2004 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.util; 020 021 import java.io.PrintWriter; 022 import java.io.StringWriter; 023 import java.lang.reflect.Method; 024 import java.util.StringTokenizer; 025 026 /** 027 * General utilities supporting the packaging of exception messages. 028 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 029 * @version 1.0.2 030 */ 031 public final class ExceptionHelper 032 { 033 // ------------------------------------------------------------------------ 034 // static 035 // ------------------------------------------------------------------------ 036 037 /** 038 * Returns the exception and causal exceptions as a formatted string. 039 * @param e the exception 040 * @return String the formatting string 041 */ 042 public static String packException( final Throwable e ) 043 { 044 return packException( null, e ); 045 } 046 047 /** 048 * Returns the exception and causal exceptions as a formatted string. 049 * @param e the exception 050 * @param stack TRUE to generate a stack trace 051 * @return String the formatting string 052 */ 053 public static String packException( final Throwable e, boolean stack ) 054 { 055 return packException( null, e, stack ); 056 } 057 058 059 /** 060 * Returns the exception and causal exceptions as a formatted string. 061 * @param message the header message 062 * @param e the exception 063 * @return String the formatting string 064 */ 065 public static String packException( final String message, final Throwable e ) 066 { 067 return packException( message, e, false ); 068 } 069 070 /** 071 * Returns the exception and causal exceptions as a formatted string. 072 * @param message the header message 073 * @param e the exception 074 * @param stack TRUE to generate a stack trace 075 * @return String the formatting string 076 */ 077 public static String packException( 078 final String message, final Throwable e, boolean stack ) 079 { 080 StringBuffer buffer = new StringBuffer(); 081 packException( buffer, 0, message, e, stack ); 082 buffer.append( getLine( END ) ); 083 return buffer.toString(); 084 } 085 086 087 /** 088 * Returns the exception and causal exceptions as a formatted string. 089 * @param message the header message 090 * @param e the exceptions 091 * @param stack TRUE to generate a stack trace 092 * @return String the formatting string 093 */ 094 public static String packException( 095 final String message, final Throwable[] e, boolean stack ) 096 { 097 final String lead = COMPOSITE + "(" + e.length + " entries) "; 098 StringBuffer buffer = new StringBuffer( getLine( lead ) ); 099 if( null != message ) 100 { 101 buffer.append( message ); 102 buffer.append( "\n" ); 103 } 104 for( int i=0; i < e.length; i++ ) 105 { 106 packException( buffer, i + 1, null, e[i], stack ); 107 } 108 buffer.append( getLine( END ) ); 109 return buffer.toString(); 110 } 111 112 // ------------------------------------------------------------------------ 113 // static implementation 114 // ------------------------------------------------------------------------ 115 116 /** 117 * Line separator character. 118 */ 119 private static final String LINE_SEPARATOR = 120 System.getProperty( "line.separator" ); 121 122 /** 123 * Header token. 124 */ 125 private static final String HEADER = "----"; 126 127 /** 128 * Exception token. 129 */ 130 private static final String EXCEPTION = HEADER + " exception report "; 131 132 /** 133 * Composite token. 134 */ 135 private static final String COMPOSITE = HEADER + " composite report "; 136 137 /** 138 * Runtime token. 139 */ 140 private static final String RUNTIME = HEADER + " runtime exception report "; 141 142 /** 143 * Error token. 144 */ 145 private static final String ERROR = HEADER + " error report "; 146 147 /** 148 * Cause token. 149 */ 150 private static final String CAUSE = HEADER + " cause "; 151 152 /** 153 * Trace token. 154 */ 155 private static final String TRACE = HEADER + " stack trace "; 156 157 /** 158 * End token. 159 */ 160 private static final String END = ""; 161 162 /** 163 * Nominal width of character display. 164 */ 165 private static final int WIDTH = 80; 166 167 /** 168 * Returns the exception and causal exceptions as a formatted string. 169 * @param buffer the string buffer 170 * @param j the causal message sequence 171 * @param message the header message 172 * @param e the exception 173 * @param stack TRUE to generate a stack trace 174 */ 175 private static void packException( 176 final StringBuffer buffer, int j, final String message, final Throwable e, boolean stack ) 177 { 178 if( e instanceof Error ) 179 { 180 buffer.append( getLine( ERROR, j ) ); 181 } 182 else if( e instanceof RuntimeException ) 183 { 184 buffer.append( getLine( RUNTIME, j ) ); 185 } 186 else 187 { 188 buffer.append( getLine( EXCEPTION, j ) ); 189 } 190 191 if( null != message ) 192 { 193 buffer.append( message ); 194 buffer.append( "\n" ); 195 } 196 197 if( e == null ) 198 { 199 return; 200 } 201 202 buffer.append( "Exception: " + e.getClass().getName() + "\n" ); 203 if( null != e.getMessage() ) 204 { 205 buffer.append( "Message: " + e.getMessage() + "\n" ); 206 } 207 packCause( buffer, getCause( e ) ).toString(); 208 Throwable root = getLastThrowable( e ); 209 if( ( root != null ) && stack ) 210 { 211 buffer.append( getLine( TRACE ) ); 212 String[] trace = captureStackTrace( root ); 213 for( int i = 0; i < trace.length; i++ ) 214 { 215 buffer.append( trace[i] + "\n" ); 216 } 217 } 218 } 219 220 /** 221 * Pack a causal exception. 222 * @param buffer the buffer to pack the exception report 223 * @param cause the causal exception to pack 224 * @return the buffer 225 */ 226 private static StringBuffer packCause( StringBuffer buffer, Throwable cause ) 227 { 228 if( cause == null ) 229 { 230 return buffer; 231 } 232 buffer.append( getLine( CAUSE ) ); 233 buffer.append( "Exception: " + cause.getClass().getName() + "\n" ); 234 buffer.append( "Message: " + cause.getMessage() + "\n" ); 235 return packCause( buffer, getCause( cause ) ); 236 } 237 238 /** 239 * Return the last throwable in the chain. 240 * @param exception the exception to extract the last throwable from 241 * @return the initiating cause 242 */ 243 private static Throwable getLastThrowable( Throwable exception ) 244 { 245 Throwable cause = getCause( exception ); 246 if( cause != null ) 247 { 248 return getLastThrowable( cause ); 249 } 250 else 251 { 252 return exception; 253 } 254 } 255 256 /** 257 * Get a causal exception using reflection. 258 * @param exception the exception 259 * @return the causal exception 260 */ 261 private static Throwable getCause( Throwable exception ) 262 { 263 if( null == exception ) 264 { 265 return null; 266 } 267 268 try 269 { 270 Class clazz = exception.getClass(); 271 Method method = clazz.getMethod( "getCause", new Class[0] ); 272 return (Throwable) method.invoke( exception, new Object[0] ); 273 } 274 catch( Throwable e ) 275 { 276 return null; 277 } 278 } 279 280 /** 281 * Captures the stack trace associated with this exception. 282 * 283 * @param throwable a <code>Throwable</code> 284 * @return an array of Strings describing stack frames. 285 */ 286 private static String[] captureStackTrace( final Throwable throwable ) 287 { 288 final StringWriter sw = new StringWriter(); 289 throwable.printStackTrace( new PrintWriter( sw, true ) ); 290 return splitString( sw.toString(), LINE_SEPARATOR ); 291 } 292 293 /** 294 * Splits the string on every token into an array of stack frames. 295 * 296 * @param string the string to split 297 * @param onToken the token to split on 298 * @return the resultant array 299 */ 300 private static String[] splitString( final String string, final String onToken ) 301 { 302 final int offset = 4; 303 final StringTokenizer tokenizer = new StringTokenizer( string, onToken ); 304 final String[] result = new String[tokenizer.countTokens()]; 305 306 for( int i = 0; i < result.length; i++ ) 307 { 308 String token = tokenizer.nextToken(); 309 if( token.startsWith( "\tat " ) ) 310 { 311 result[i] = token.substring( offset ); 312 } 313 else 314 { 315 result[i] = token; 316 } 317 } 318 319 return result; 320 } 321 322 /** 323 * Return a line of '-' characters. 324 * @param lead the lead 325 * @return the line 326 */ 327 private static String getLine( String lead ) 328 { 329 return getLine( lead, 0 ); 330 } 331 332 /** 333 * Get a line of '-' characters with padded offset. 334 * @param lead the leading characters 335 * @param count the number of characters to fill 336 * @return the filled out line 337 */ 338 private static String getLine( String lead, int count ) 339 { 340 StringBuffer buffer = new StringBuffer( lead ); 341 int q = 0; 342 if( count > 0 ) 343 { 344 String v = "" + count + " "; 345 buffer.append( "" + count ); 346 buffer.append( " " ); 347 q = v.length() + 1; 348 } 349 int j = WIDTH - ( lead.length() + q ); 350 for( int i=0; i < j; i++ ) 351 { 352 buffer.append( "-" ); 353 } 354 buffer.append( "\n" ); 355 return buffer.toString(); 356 } 357 358 /** 359 * Disabled. 360 */ 361 private ExceptionHelper() 362 { 363 // disable 364 } 365 }